Add x402 v2 client support with the upto (Permit2) scheme#17
Draft
claude[bot] wants to merge 1 commit into
Draft
Add x402 v2 client support with the upto (Permit2) scheme#17claude[bot] wants to merge 1 commit into
claude[bot] wants to merge 1 commit into
Conversation
Introduce a scheme/version handler registry in src/lib/x402 so payment
schemes are pluggable instead of hard-branched. The existing EIP-3009
logic becomes an exact@v1 handler (behavior preserved) and a new upto@v2
handler signs a Uniswap Permit2 permitWitnessTransferFrom authorization
up to the advertised maximum, which the facilitator settles for actual
usage.
- protocol.ts: parse v1 (maxAmountRequired) and v2 (amount) challenges,
CAIP-2 network parsing/matching, v2 PAYMENT-REQUIRED / PAYMENT-SIGNATURE
/ PAYMENT-RESPONSE header names, settled-amount decoding.
- permit2.ts: canonical Permit2 + x402UptoPermit2Proxy constants, EIP-712
types (Witness order to/facilitator/validAfter, confirmed against the
deployed proxy), name-only Permit2 domain, authorization builder +
signer using viem signTypedData.
- handlers.ts: SchemeHandler registry, exact@v1 and upto@v2 handlers,
Permit2 allowance read helper.
- walletX402.ts: route v1/v2 off x402Version, select a supported
{scheme,network} instead of filtering to exact, Permit2 allowance
preflight + approve flow (--x402-approve-permit2), 412 handling, display
the actual settled amount.
- Tests for v2 parse, CAIP-2, handler selection, upto payload shape,
Permit2 typed-data recovery, and missing-facilitator handling.
- README: document v1/v2 and the upto scheme.
ENG-2067
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012td7BjwxkhNazzdcVTGn74
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Kyle Crawshaw · Slack thread
Implements client-side x402 v2 support, including the
uptoscheme. Server/facilitator and contracts are out of scope — this is the CLI client only. Closes ENG-2067.Before / After
Before:
wallet x402 <verb> <url>only spoke x402 v1 with the fixed-priceexactscheme (EIP-3009transferWithAuthorization).pickAccepthard-filtered the server's offers toscheme === 'exact'and silently dropped everything else, so a server that advertised anupto402 was simply reported as "no compatible payment option" — the request failed.After: the CLI detects v1 vs v2 from the advertised
x402Versionand routes accordingly (v1 servers keep working unchanged). For a v2upto402 it signs a Uniswap Permit2permitWitnessTransferFromauthorization up to the advertised maximum; the facilitator then settles the actual usage (which may be less than the max, or zero for a $0 settle). The CLI surfaces the real settled amount from the response.How
src/lib/x402/handlers.ts): aSchemeHandlerinterface (version,scheme,canHandle(accept, chainId),buildPayload(accept, ctx) -> { headerName, headerValueBase64 }). The existing EIP-3009 logic is refactored into anexact@v1handler (behavior preserved); a newupto@v2handler builds the Permit2 payload.selectHandlerpicks the first handler that supports an offered{scheme, network}on the configured chain.src/lib/x402/protocol.ts): parses both v1 (maxAmountRequired) and v2 (amount) challenges onto one normalized field; reads the base64PAYMENT-REQUIREDchallenge (header or body); emitsPAYMENT-SIGNATURE; decodesPAYMENT-RESPONSEincluding the actualamount. CAIP-2 helpers mapeip155:<id>↔ chain id and reconcile against the CLI's--network.src/lib/x402/permit2.ts): named constants for the canonical Permit2 (0x0000…78BA3) and thex402UptoPermit2Proxyspender (0x4020…0002); EIP-712 types with theWitness(to, facilitator, validAfter)ordering; random 256-bit nonce,deadline/validAfterhandling; signed via viemsignTypedData(no new crypto dep).walletX402.ts): reads the payer's ERC-20 allowance for the canonical Permit2 before signing; if short, offers to send the one-timeapprove(prompt, or auto with--x402-approve-permit2/-y). Also handles a412from the facilitator with a clear "allowance required" message.walletX402.ts): removed the exact-only filter; threshold/auto-pay compares against the authorized max forupto; the "no compatible payment option" error now listsexact@v1, upto@v2; the settled amount from the v2 response is displayed.Files touched:
src/lib/x402/protocol.ts,src/lib/x402/permit2.ts(new),src/lib/x402/handlers.ts(new),src/commands/walletX402.ts,tests/x402-protocol.test.ts,tests/x402-upto.test.ts(new),README.md.Testing
npm test— 43 passed (6 files). New/updated coverage: v1+v2 challenge parse, CAIP-2 parse/match, handler selection (uptonow selectable, unsupported schemes rejected),uptopayload encode (asserts exact field names/shape and string types), Permit2 typed-data construction recovering to the signer, and the missing-facilitatorAddresserror path.npm run build(tsc) — clean, no errors. No lint config in the repo.Caveats / follow-ups
Witness(address to, address facilitator, uint256 validAfter)member order was verified against the deployedx402UptoPermit2Proxysource (WITNESS_TYPEHASH = keccak256("Witness(address to,address facilitator,uint256 validAfter)")). It lives as a single source-of-truth constant (PERMIT2_UPTO_TYPESinpermit2.ts) if it ever needs to change.version. The task brief suggested aversion: "1"domain field, but the canonical Uniswap Permit2EIP712Domainis name-only (name,chainId,verifyingContract) — adding aversionwould change the domain separator and produce a signature the contract/facilitator rejects. I followed the on-chain reality (name-only) and documented this decision at the signer.exact@v2scoped out. The v2 framing of the EIP-3009exactpayload isn't pinned down in the spec available, so rather than guess a payload shape that signs real value, it's left as a clearly-marked TODO in the handler registry.upto@v2is the solid v2 deliverable. Easy follow-up once theexact@v2payload shape is confirmed.approveis sent formaxUint256(standard one-time unlimited approval, matching common Permit2 UX). If a scoped approval is preferred, that's a one-line change.🤖 Generated with Claude Code
https://claude.ai/code/session_012td7BjwxkhNazzdcVTGn74
Generated by Claude Code